home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 5 / Skunkware 5.iso / src / Tools / bsdinstall / bsdinstall.c < prev    next >
Encoding:
C/C++ Source or Header  |  1995-06-18  |  15.2 KB  |  696 lines

  1. /*
  2.  * Copyright (c) 1987 Regents of the University of California.
  3.  * All rights reserved.
  4.  *
  5.  * Redistribution and use in source and binary forms, with or without
  6.  * modification, are permitted provided that the following conditions
  7.  * are met:
  8.  * 1. Redistributions of source code must retain the above copyright
  9.  *    notice, this list of conditions and the following disclaimer.
  10.  * 2. Redistributions in binary form must reproduce the above copyright
  11.  *    notice, this list of conditions and the following disclaimer in the
  12.  *    documentation and/or other materials provided with the distribution.
  13.  * 3. All advertising materials mentioning features or use of this software
  14.  *    must display the following acknowledgement:
  15.  *    This product includes software developed by the University of
  16.  *    California, Berkeley and its contributors.
  17.  * 4. Neither the name of the University nor the names of its contributors
  18.  *    may be used to endorse or promote products derived from this software
  19.  *    without specific prior written permission.
  20.  *
  21.  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
  22.  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  23.  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  24.  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
  25.  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  26.  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
  27.  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  28.  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  29.  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
  30.  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  31.  * SUCH DAMAGE.
  32.  */
  33.  
  34. #ifndef lint
  35. char copyright[] =
  36. "@(#) Copyright (c) 1987 Regents of the University of California.\n\
  37.  All rights reserved.\n";
  38. #endif /* not lint */
  39.  
  40. #ifndef lint
  41. static char sccsid[] = "@(#)xinstall.c    5.24 (Berkeley) 7/1/90";
  42. #endif /* not lint */
  43.  
  44. #include <sys/param.h>
  45. #include <sys/stat.h>
  46. #include <sys/file.h>
  47. #include <grp.h>
  48. #include <pwd.h>
  49. #include <stdio.h>
  50. #include <ctype.h>
  51. #ifdef _SCO_DS
  52. #include <fcntl.h>
  53. #include <errno.h>
  54. #include <stdlib.h>
  55. #define MAXPATHLEN    1024
  56. #define MAXBSIZE    4096
  57. #define _PATH_STRIP    "/usr/bin/strip"
  58. #define _PATH_DEVNULL    "/dev/null"
  59. #define S_ISTXT        S_ISVTX
  60. mode_t            getmode();
  61. void            *setmode();
  62. #else
  63. #include <paths.h>
  64. #include "pathnames.h"
  65. #endif
  66.  
  67. static struct passwd *pp;
  68. static struct group *gp;
  69. static int docopy, dostrip, mode = 0755;
  70. static char *group, *owner, pathbuf[MAXPATHLEN];
  71.  
  72. main(argc, argv)
  73.     int argc;
  74.     char **argv;
  75. {
  76.     extern char *optarg;
  77.     extern int optind;
  78.     struct stat from_sb, to_sb;
  79.     mode_t *set;
  80.     int ch, no_target;
  81.     char *to_name;
  82.  
  83.     while ((ch = getopt(argc, argv, "cg:m:o:s")) != EOF)
  84.         switch((char)ch) {
  85.         case 'c':
  86.             docopy = 1;
  87.             break;
  88.         case 'g':
  89.             group = optarg;
  90.             break;
  91.         case 'm':
  92.             if (!(set = setmode(optarg))) {
  93.                 (void)fprintf(stderr,
  94.                     "install: invalid file mode.\n");
  95.                 exit(1);
  96.             }
  97.             mode = getmode(set, 0);
  98.             break;
  99.         case 'o':
  100.             owner = optarg;
  101.             break;
  102.         case 's':
  103.             dostrip = 1;
  104.             break;
  105.         case '?':
  106.         default:
  107.             usage();
  108.         }
  109.     argc -= optind;
  110.     argv += optind;
  111.     if (argc < 2)
  112.         usage();
  113.  
  114.     /* get group and owner id's */
  115.     if (group && !(gp = getgrnam(group))) {
  116.         fprintf(stderr, "install: unknown group %s.\n", group);
  117.         exit(1);
  118.     }
  119.     if (owner && !(pp = getpwnam(owner))) {
  120.         fprintf(stderr, "install: unknown user %s.\n", owner);
  121.         exit(1);
  122.     }
  123.  
  124.     no_target = stat(to_name = argv[argc - 1], &to_sb);
  125.     if (!no_target && (to_sb.st_mode & S_IFMT) == S_IFDIR) {
  126.         for (; *argv != to_name; ++argv)
  127.             install(*argv, to_name, 1);
  128.         exit(0);
  129.     }
  130.  
  131.     /* can't do file1 file2 directory/file */
  132.     if (argc != 2)
  133.         usage();
  134.  
  135.     if (!no_target) {
  136.         if (stat(*argv, &from_sb)) {
  137.             fprintf(stderr, "install: can't find %s.\n", *argv);
  138.             exit(1);
  139.         }
  140.         if ((to_sb.st_mode & S_IFMT) != S_IFREG) {
  141.             fprintf(stderr, "install: %s isn't a regular file.\n", to_name);
  142.             exit(1);
  143.         }
  144.         if (to_sb.st_dev == from_sb.st_dev && to_sb.st_ino == from_sb.st_ino) {
  145.             fprintf(stderr, "install: %s and %s are the same file.\n", *argv, to_name);
  146.             exit(1);
  147.         }
  148.         /* unlink now... avoid ETXTBSY errors later */
  149.         (void)unlink(to_name);
  150.     }
  151.     install(*argv, to_name, 0);
  152.     exit(0);
  153. }
  154.  
  155. /*
  156.  * install --
  157.  *    build a path name and install the file
  158.  */
  159. install(from_name, to_name, isdir)
  160.     char *from_name, *to_name;
  161.     int isdir;
  162. {
  163.     struct stat from_sb;
  164.     int devnull, from_fd, to_fd;
  165.     char *C, *rindex();
  166.  
  167.     /* if try to install NULL file to a directory, fails */
  168.     if (isdir || strcmp(from_name, _PATH_DEVNULL)) {
  169.         if (stat(from_name, &from_sb)) {
  170.             fprintf(stderr, "install: can't find %s.\n", from_name);
  171.             exit(1);
  172.         }
  173.         if ((from_sb.st_mode & S_IFMT) != S_IFREG) {
  174.             fprintf(stderr, "install: %s isn't a regular file.\n", from_name);
  175.             exit(1);
  176.         }
  177.         /* build the target path */
  178.         if (isdir) {
  179.             (void)sprintf(pathbuf, "%s/%s", to_name, (C = rindex(from_name, '/')) ? ++C : from_name);
  180.             to_name = pathbuf;
  181.         }
  182.         devnull = 0;
  183.     } else
  184.         devnull = 1;
  185.  
  186.     /* unlink now... avoid ETXTBSY errors later */
  187.     (void)unlink(to_name);
  188.  
  189.     /* create target */
  190.     if ((to_fd = open(to_name, O_CREAT|O_WRONLY|O_TRUNC, 0600)) < 0) {
  191.         error(to_name);
  192.         exit(1);
  193.     }
  194.     if (!devnull) {
  195.         if ((from_fd = open(from_name, O_RDONLY, 0)) < 0) {
  196.             (void)unlink(to_name);
  197.             error(from_name);
  198.             exit(1);
  199.         }
  200.         copy(from_fd, from_name, to_fd, to_name);
  201.         (void)close(from_fd);
  202.     }
  203.     if (dostrip)
  204.         strip(to_name);
  205.     /*
  206.      * set owner, group, mode for target; do the chown first,
  207.      * chown may lose the setuid bits.
  208.      */
  209.     if ((group || owner) &&
  210.         fchown(to_fd, owner ? pp->pw_uid : -1, group ? gp->gr_gid : -1) ||
  211.         fchmod(to_fd, mode)) {
  212.         error(to_name);
  213.         bad(to_name);
  214.     }
  215.     (void)close(to_fd);
  216.     if (!docopy && !devnull && unlink(from_name)) {
  217.         error(from_name);
  218.         exit(1);
  219.     }
  220. }
  221.  
  222. /*
  223.  * copy --
  224.  *    copy from one file to another
  225.  */
  226. copy(from_fd, from_name, to_fd, to_name)
  227.     register int from_fd, to_fd;
  228.     char *from_name, *to_name;
  229. {
  230.     register int n;
  231.     char buf[MAXBSIZE];
  232.  
  233.     while ((n = read(from_fd, buf, sizeof(buf))) > 0)
  234.         if (write(to_fd, buf, n) != n) {
  235.             error(to_name);
  236.             bad(to_name);
  237.         }
  238.     if (n == -1) {
  239.         error(from_name);
  240.         bad(to_name);
  241.     }
  242. }
  243.  
  244. /*
  245.  * strip --
  246.  *    use strip(1) to strip the target file
  247.  */
  248. strip(to_name)
  249.     char *to_name;
  250. {
  251.     int status;
  252.  
  253.     switch (vfork()) {
  254.     case -1:
  255.         error("fork");
  256.         bad(to_name);
  257.     case 0:
  258.         execl(_PATH_STRIP, "strip", to_name, (char *)NULL);
  259.         error(_PATH_STRIP);
  260.         _exit(1);
  261.     default:
  262.         if (wait(&status) == -1 || status)
  263.             bad(to_name);
  264.     }
  265. }
  266.  
  267. /*
  268.  * error --
  269.  *    print out an error message
  270.  */
  271. error(s)
  272.     char *s;
  273. {
  274.     extern int errno;
  275.     char *strerror();
  276.  
  277.     (void)fprintf(stderr, "install: %s: %s\n", s, strerror(errno));
  278. }
  279.  
  280. /*
  281.  * bad --
  282.  *    remove created target and die
  283.  */
  284. bad(fname)
  285.     char *fname;
  286. {
  287.     (void)unlink(fname);
  288.     exit(1);
  289. }
  290.  
  291. /*
  292.  * usage --
  293.  *    print a usage message and die
  294.  */
  295. usage()
  296. {
  297.     (void)fprintf(stderr,
  298. "usage: install [-cs] [-g group] [-m mode] [-o owner] file1 file2;\n\tor file1 ... fileN directory\n");
  299.     exit(1);
  300. }
  301.  
  302. #define    SET_LEN    6        /* initial # of bitcmd struct to malloc */
  303. #define    SET_LEN_INCR 4        /* # of bitcmd structs to add as needed */
  304.  
  305. struct bitcmd {
  306.     char    cmd;
  307.     char    cmd2;
  308.     mode_t    bits;
  309. };
  310.  
  311. #define    CMD2_CLR    0x01
  312. #define    CMD2_SET    0x02
  313. #define    CMD2_GBITS    0x04
  314. #define    CMD2_OBITS    0x08
  315. #define    CMD2_UBITS    0x10
  316.  
  317. /*
  318.  * Given the old mode and an array of bitcmd structures, apply the operations
  319.  * described in the bitcmd structures to the old mode, and return the new mode.
  320.  * Note that there is no '=' command; a strict assignment is just a '-' (clear
  321.  * bits) followed by a '+' (set bits).
  322.  */
  323. mode_t
  324. getmode(bbox, omode)
  325.     void *bbox;
  326.     mode_t omode;
  327. {
  328.     register struct bitcmd *set;
  329.     register mode_t newmode, value;
  330.  
  331.     set = (struct bitcmd *)bbox;
  332.     newmode = omode;
  333.     for (value = 0;; set++)
  334.         switch(set->cmd) {
  335.         /*
  336.          * When copying the user, group or other bits around, we "know"
  337.          * where the bit are in the mode so that we can do shifts to
  338.          * copy them around.  If we don't use shifts, it gets real
  339.          * grundgy with lots of single bit checks and bit sets.
  340.          */
  341.         case 'u':
  342.             value = (newmode & S_IRWXU) >> 6;
  343.             goto common;
  344.  
  345.         case 'g':
  346.             value = (newmode & S_IRWXG) >> 3;
  347.             goto common;
  348.  
  349.         case 'o':
  350.             value = newmode & S_IRWXO;
  351.         common:
  352.             if (set->cmd2 & CMD2_CLR) {
  353.                 if (set->cmd2 & CMD2_UBITS)
  354.                     newmode &= ~(S_IRWXU & set->bits);
  355.                 if (set->cmd2 & CMD2_GBITS)
  356.                     newmode &= ~(S_IRWXG & set->bits);
  357.                 if (set->cmd2 & CMD2_OBITS)
  358.                     newmode &= ~(S_IRWXO & set->bits);
  359.             }
  360.             if (set->cmd2 & CMD2_SET) {
  361.                 if (set->cmd2 & CMD2_UBITS)
  362.                     newmode |= (value<<6) & set->bits;
  363.                 if (set->cmd2 & CMD2_GBITS)
  364.                     newmode |= (value<<3) & set->bits;
  365.                 if (set->cmd2 & CMD2_OBITS)
  366.                     newmode |= value & set->bits;
  367.             }
  368.             break;
  369.  
  370.         case '+':
  371.             newmode |= set->bits;
  372.             break;
  373.  
  374.         case '-':
  375.             newmode &= ~set->bits;
  376.             break;
  377.  
  378.         case 'X':
  379.             if (omode & (S_IFDIR|S_IXUSR|S_IXGRP|S_IXOTH))
  380.                 newmode |= set->bits;
  381.             break;
  382.  
  383.         case '\0':
  384.         default:
  385. #ifdef SETMODE_DEBUG
  386.             (void)printf("getmode(, %04o) -> %04o\n",
  387.                 omode, newmode);
  388. #endif
  389.             return(newmode);
  390.         }
  391. }
  392.  
  393. #define    STANDARD_BITS    (S_ISUID|S_ISGID|S_IRWXU|S_IRWXG|S_IRWXO)
  394.  
  395. static struct bitcmd *
  396. addcmd(set, op, who, oparg, mask)
  397.     struct bitcmd *set;
  398.     register int oparg, who;
  399.     register int op;
  400.     mode_t mask;
  401. {
  402.     switch (op) {
  403.     case '+':
  404.     case 'X':
  405.         set->cmd = op;
  406.         set->bits = (who ? who : mask) & oparg;
  407.         break;
  408.  
  409.     case '-':
  410.         set->cmd = '-';
  411.         set->bits = (who ? who : (S_IRWXU|S_IRWXG|S_IRWXO)) & oparg;
  412.         break;
  413.  
  414.     case '=':
  415.         set->cmd = '-';
  416.         if (!who) {
  417.             set->bits = STANDARD_BITS;
  418.             who = mask;
  419.         } else
  420.             set->bits = who;
  421.         set++;
  422.  
  423.         set->cmd = '+';
  424.         set->bits = who & oparg;
  425.         break;
  426.     case 'u':
  427.     case 'g':
  428.     case 'o':
  429.         set->cmd = op;
  430.         if (who) {
  431.             set->cmd2 = ((who & S_IRUSR) ? CMD2_UBITS : 0) |
  432.                     ((who & S_IRGRP) ? CMD2_GBITS : 0) |
  433.                     ((who & S_IROTH) ? CMD2_OBITS : 0);
  434.             set->bits = ~0;
  435.         } else {
  436.             set->cmd2 = CMD2_UBITS | CMD2_GBITS | CMD2_OBITS;
  437.             set->bits = mask;
  438.         }
  439.     
  440.         if (oparg == '+')
  441.             set->cmd2 |= CMD2_SET;
  442.         else if (oparg == '-')
  443.             set->cmd2 |= CMD2_CLR;
  444.         else if (oparg == '=')
  445.             set->cmd2 |= CMD2_SET|CMD2_CLR;
  446.         break;
  447.     }
  448.     return(set+1);
  449. }
  450.  
  451. #define    ADDCMD(a, b, c, d) \
  452.     if (set >= endset) { \
  453.         register struct bitcmd *newset; \
  454.         setlen += SET_LEN_INCR; \
  455.         newset = realloc(saveset, sizeof(struct bitcmd) * setlen); \
  456.         if (!saveset) \
  457.             return(NULL); \
  458.         set = newset + (set - saveset); \
  459.         saveset = newset; \
  460.         endset = newset + (setlen - 2); \
  461.     } \
  462.     set = addcmd(set, (a), (b), (c), (d))
  463.  
  464. void *
  465. setmode(p)
  466.     register char *p;
  467. {
  468.     register int perm, who;
  469.     register char op;
  470.     mode_t mask;
  471.     struct bitcmd *set, *saveset, *endset;
  472.     int permXbits, setlen;
  473.     static int compress_mode();
  474.  
  475.     /*
  476.      * Get a copy of the mask for the permissions that are mask relative.
  477.      * Flip the bits, we want what's not set.
  478.      */
  479.     (void)umask(mask = umask(0));
  480.     mask = ~mask;
  481.  
  482.     setlen = SET_LEN + 2;
  483.     
  484.     set = (struct bitcmd *)malloc((u_int)(sizeof(struct bitcmd) * setlen));
  485.     if (!set)
  486.         return(NULL);
  487.     saveset = set;
  488.     endset = set + (setlen - 2);
  489.  
  490.     /*
  491.      * If an absolute number, get it and return; disallow non-octal digits
  492.      * or illegal bits.
  493.      */
  494.     if (isdigit(*p)) {
  495.         perm = (mode_t)strtol(p, (char **)0, 8);
  496.         if (perm & ~(STANDARD_BITS|S_ISTXT)) {
  497.             free(saveset);
  498.             return(NULL);
  499.         }
  500.         while (*++p)
  501.             if (*p < '0' || *p > '7') {
  502.                 free(saveset);
  503.                 return(NULL);
  504.             }
  505.         ADDCMD('=', (STANDARD_BITS|S_ISTXT), perm, mask);
  506.         return((void *)saveset);
  507.     }
  508.  
  509.     if (!*p) {
  510.         free(saveset);
  511.         return(NULL);
  512.     }
  513.     /*
  514.      * Build list of structures to set/clear/copy bits as described by
  515.      * each clause of the symbolic mode.
  516.      */
  517.     for (;;) {
  518.         /* First, find out which bits might be modified. */
  519.         for (who = 0;; ++p) {
  520.             switch (*p) {
  521.             case 'a':
  522.                 who |= STANDARD_BITS;
  523.                 break;
  524.             case 'u':
  525.                 who |= S_ISUID|S_IRWXU;
  526.                 break;
  527.             case 'g':
  528.                 who |= S_ISGID|S_IRWXG;
  529.                 break;
  530.             case 'o':
  531.                 who |= S_IRWXO;
  532.                 break;
  533.             default:
  534.                 goto getop;
  535.             }
  536.         }
  537.     getop:        
  538.  
  539.         if ((op = *p++) != '+' && op != '-' && op != '=') {
  540.             free(saveset);
  541.             return(NULL);
  542.         }
  543.  
  544.         who &= ~S_ISTXT;
  545.         for (perm = 0, permXbits = 0;; ++p) {
  546.             switch (*p) {
  547.             case 'r':
  548.                 perm |= S_IRUSR|S_IRGRP|S_IROTH;
  549.                 break;
  550.             case 's':
  551.                 /* If only "other" bits ignore set-id. */
  552.                 if (who & ~S_IRWXO)
  553.                     perm |= S_ISUID|S_ISGID;
  554.                 break;
  555.             case 't':
  556.                 /* If only "other" bits ignore sticky. */
  557.                 if (who & ~S_IRWXO) {
  558.                     who |= S_ISTXT;
  559.                     perm |= S_ISTXT;
  560.                 }
  561.                 break;
  562.             case 'w':
  563.                 perm |= S_IWUSR|S_IWGRP|S_IWOTH;
  564.                 break;
  565.             case 'X':
  566.                 permXbits = S_IXUSR|S_IXGRP|S_IXOTH;
  567.                 break;
  568.             case 'x':
  569.                 perm |= S_IXUSR|S_IXGRP|S_IXOTH;
  570.                 break;
  571.             case 'u':
  572.             case 'g':
  573.             case 'o':
  574.                 /*
  575.                  * When ever we hit 'u', 'g', or 'o', we have
  576.                  * to flush out any partial mode that we have,
  577.                  * and then do the copying of the mode bits.
  578.                  */
  579.                 if (perm) {
  580.                     ADDCMD(op, who, perm, mask);
  581.                     perm = 0;
  582.                 }
  583.                 if (op == '+' && permXbits) {
  584.                     ADDCMD('X', who, permXbits, mask);
  585.                     permXbits = 0;
  586.                 }
  587.                 ADDCMD(*p, who, op, mask);
  588.                 break;
  589.  
  590.             default:
  591.                 /*
  592.                  * Add any permissions that we haven't already
  593.                  * done.
  594.                  */
  595.                 if (perm) {
  596.                     ADDCMD(op, who, perm, mask);
  597.                     perm = 0;
  598.                 }
  599.                 if (permXbits) {
  600.                     ADDCMD('X', who, permXbits, mask);
  601.                     permXbits = 0;
  602.                 }
  603.                 goto apply;
  604.             }
  605.         }
  606.  
  607. apply:        if (!*p)
  608.             break;
  609.         if (*p != ',')
  610.             goto getop;
  611.         ++p;
  612.     }
  613.     set->cmd = 0;
  614. #ifdef SETMODE_DEBUG
  615.     (void)printf("Before compress_mode()\n");
  616.     dumpmode(saveset);
  617. #endif
  618.     compress_mode(saveset);
  619. #ifdef SETMODE_DEBUG
  620.     (void)printf("After compress_mode()\n");
  621.     dumpmode(saveset);
  622. #endif
  623.     return((void *)saveset);
  624. }
  625.  
  626. #ifdef SETMODE_DEBUG
  627. dumpmode(set)
  628.     register struct bitcmd *set;
  629. {
  630.     for (; set->cmd; ++set)
  631.         (void)printf("cmd: '%c' bits %04o%s%s%s%s%s%s\n",
  632.             set->cmd, set->bits, set->cmd2 ? " cmd2:" : "",
  633.             set->cmd2 & CMD2_CLR ? " CLR" : "",
  634.             set->cmd2 & CMD2_SET ? " SET" : "",
  635.             set->cmd2 & CMD2_UBITS ? " UBITS" : "",
  636.             set->cmd2 & CMD2_GBITS ? " GBITS" : "",
  637.             set->cmd2 & CMD2_OBITS ? " OBITS" : "");
  638. }
  639. #endif
  640.  
  641. /*
  642.  * Given an array of bitcmd structures, compress by compacting consecutive
  643.  * '+', '-' and 'X' commands into at most 3 commands, one of each.  The 'u',
  644.  * 'g' and 'o' commands continue to be separate.  They could probably be 
  645.  * compacted, but it's not worth the effort.
  646.  */
  647. static
  648. compress_mode(set)
  649.     register struct bitcmd *set;
  650. {
  651.     register struct bitcmd *nset;
  652.     register int setbits, clrbits, Xbits, op;
  653.  
  654.     for (nset = set;;) {
  655.         /* Copy over any 'u', 'g' and 'o' commands. */
  656.         while ((op = nset->cmd) != '+' && op != '-' && op != 'X') {
  657.             *set++ = *nset++;
  658.             if (!op)
  659.                 return;
  660.         }
  661.  
  662.         for (setbits = clrbits = Xbits = 0;; nset++) {
  663.             if ((op = nset->cmd) == '-') {
  664.                 clrbits |= nset->bits;
  665.                 setbits &= ~nset->bits;
  666.                 Xbits &= ~nset->bits;
  667.             } else if (op == '+') {
  668.                 setbits |= nset->bits;
  669.                 clrbits &= ~nset->bits;
  670.                 Xbits &= ~nset->bits;
  671.             } else if (op == 'X')
  672.                 Xbits |= nset->bits & ~setbits;
  673.             else
  674.                 break;
  675.         }
  676.         if (clrbits) {
  677.             set->cmd = '-';
  678.             set->cmd2 = 0;
  679.             set->bits = clrbits;
  680.             set++;
  681.         }
  682.         if (setbits) {
  683.             set->cmd = '+';
  684.             set->cmd2 = 0;
  685.             set->bits = setbits;
  686.             set++;
  687.         }
  688.         if (Xbits) {
  689.             set->cmd = 'X';
  690.             set->cmd2 = 0;
  691.             set->bits = Xbits;
  692.             set++;
  693.         }
  694.     }
  695. }
  696.